package com.keqiang.chart.impl.axis.impl.bar

import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.text.TextPaint
import com.keqiang.chart.animation.Animator
import com.keqiang.chart.impl.axis.Axis
import com.keqiang.chart.impl.axis.impl.line.AddedLine
import com.keqiang.chart.impl.axis.render.BaseDataRender
import com.keqiang.chart.utils.ObjectPool
import com.keqiang.utils.PaintUtils.getChartHeight

/**
 * 绘制柱状图柱形图
 *
 * @author Created by wanggaowan on 2022/7/26 11:33
 */
open class BarChartDataRender(chart: BarChart) : BaseDataRender<BarChart>(chart) {

    override fun onDraw(canvas: Canvas, start: Float, top: Float, end: Float, bottom: Float,
                        gridWidth: Float, gridHeight: Float, animator: Animator?) {
        val barData = chart.data ?: return

        canvas.save()
        canvas.clipRect(start - chart.xAxis.startPadding,
            top - chart.yAxis.endPadding,
            end + chart.xAxis.endPadding,
            bottom + chart.yAxis.startPadding)

        barData.groups?.let {
            drawBar(canvas, it, start, gridWidth, top, bottom, animator)
            drawValueText(canvas, it, top)
        }

        if ((animator?.getProgress() ?: 1f) == 1f) {
            barData.addedLines?.let {
                drawAddedLine(canvas, it, start, end, top, bottom)
            }
        }

        canvas.restore()

        if ((animator?.getProgress() ?: 1f) == 1f) {
            chart.touchHandler?.touchData?.let {
                chart.highLightRender?.onDraw(canvas, start, top, end, bottom, it)
            }
        }
    }

    /**
     * 绘制柱状图
     */
    protected open fun drawBar(canvas: Canvas, dataList: List<BarGroup>, start: Float, gridWidth: Float, top: Float, bottom: Float, animator: Animator?) {
        val yAxis = chart.yAxis
        var tempStart = start
        val range = bottom - top
        val maxValue = yAxis.max - yAxis.min
        val paint = ObjectPool.obtainPaint<Paint>()
        paint.style = Paint.Style.FILL

        for (data in dataList) {
            if (data.values.isEmpty()) {
                continue
            }

            var totalPercent = 0f
            data.values.forEach { entity ->
                totalPercent += entity.widthPercent
            }

            val width = gridWidth - data.barOffset * (data.values.size - 1)
            var drawStart = if (totalPercent >= 1 || data.gravity == BarGroup.GRAVITY_START) {
                tempStart
            } else if (data.gravity == BarGroup.GRAVITY_END) {
                tempStart + width * (1 - totalPercent)
            } else {
                tempStart + width * (1 - totalPercent) / 2f
            }

            data.values.forEach { entity ->
                val barWidth = entity.widthPercent * width
                val alpha = Color.alpha(entity.bgColor)
                if (alpha > 0) {
                    entity.drawRectF.set(drawStart, top, drawStart + barWidth, bottom)
                    paint.color = entity.bgColor
                    paint.shader = null
                    canvas.drawRect(entity.drawRectF, paint)
                }

                val barHeight =
                    (entity.value - yAxis.min) / maxValue * range * (animator?.getProgress() ?: 1f)
                entity.drawRectF.set(drawStart, bottom - barHeight, drawStart + barWidth, bottom)
                paint.shader =
                    entity.shaderFactory?.invoke(entity.drawRectF.left, top, entity.drawRectF.right, bottom)
                paint.color = entity.color
                canvas.drawRect(entity.drawRectF, paint)

                drawStart += barWidth + data.barOffset
            }

            tempStart += gridWidth
        }

        ObjectPool.recyclePaint(paint)
    }

    /**
     * 绘制柱状图文本
     */
    protected open fun drawValueText(canvas: Canvas, dataList: List<BarGroup>, top: Float) {
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)
        textPaint.textAlign = Paint.Align.CENTER
        for (data in dataList) {
            if (data.values.isEmpty()) {
                continue
            }

            data.values.forEach { entity ->
                if (entity.drawLabel) {
                    val text = entity.label
                    if (!text.isNullOrEmpty()) {
                        textPaint.color = entity.labelColor
                        textPaint.textSize = entity.labelTextSize

                        var y = if (entity.labelFixTop) {
                            top - entity.labelOffset
                        } else {
                            entity.drawRectF.top - entity.labelOffset
                        }

                        val textHeight = textPaint.getChartHeight(includePadding = false)
                        if (y - textHeight < top - chart.yAxis.endPadding) {
                            y = entity.drawRectF.top + textHeight + entity.labelOffset
                        }

                        canvas.drawText(text, entity.drawRectF.left + entity.drawRectF.width() / 2, y, textPaint)
                    }
                }
            }
        }

        ObjectPool.recyclePaint(textPaint)
    }

    /**
     * [end]： 折线图X轴绘制区域结束点，不包括[Axis.endPadding]
     *
     * [top]： 折线图Y轴绘制区域起始点，不包括[Axis.endPadding]
     *
     * [animProgress]:以动画形式绘制，指定动画进度
     */
    protected open fun drawAddedLine(
        canvas: Canvas, addedLines: List<AddedLine>,
        start: Float, end: Float, top: Float,
        bottom: Float, animProgress: Float = 1f
    ) {

        val tempRectF = ObjectPool.obtainRectF()
        val tempPath = ObjectPool.obtainPath()
        val textPaint = ObjectPool.obtainPaint<TextPaint>(true)

        canvas.save()
        tempRectF.set(start, top - chart.yAxis.endPadding, end, bottom)
        canvas.clipRect(tempRectF)

        val yAxis = chart.yAxis
        val xAxis = chart.xAxis
        addedLines.forEach {
            val y = bottom - ((it.value - yAxis.min) / (yAxis.max - yAxis.min) * (bottom - top))
            val stopX = if (it.useEndPadding) end else end + xAxis.endPadding
            val startX = if (it.useStartPadding) start else start - xAxis.startPadding
            textPaint.color = it.lineColor
            textPaint.strokeWidth = it.lineWidth
            textPaint.style = Paint.Style.STROKE
            textPaint.pathEffect = it.pathEffect
            tempPath.reset()
            tempPath.moveTo(startX, y)
            tempPath.lineTo(stopX * animProgress, y)
            canvas.drawPath(tempPath, textPaint)

            if (animProgress < 1f || it.label.isNullOrEmpty() || it.labelTextSize <= 0) {
                return@forEach
            }

            val isHorizontalCenter =
                it.labelGravity and android.view.Gravity.LEFT != android.view.Gravity.LEFT
                    && it.labelGravity and android.view.Gravity.RIGHT != android.view.Gravity.RIGHT
                    && it.labelGravity and android.view.Gravity.CENTER_HORIZONTAL == android.view.Gravity.CENTER_HORIZONTAL
            val isEnd =
                !isHorizontalCenter && (it.labelGravity and android.view.Gravity.RIGHT) == android.view.Gravity.RIGHT
            val isBottom =
                (it.labelGravity and android.view.Gravity.BOTTOM) == android.view.Gravity.BOTTOM

            textPaint.style = Paint.Style.FILL
            textPaint.pathEffect = null
            textPaint.color = it.labelTextColor
            textPaint.textSize = it.labelTextSize
            var textStartX: Float = it.labelXOffset
            textPaint.textAlign = when {
                isHorizontalCenter -> {
                    textStartX += startX + (stopX - startX) / 2f
                    Paint.Align.CENTER
                }

                isEnd -> {
                    textStartX += stopX
                    Paint.Align.RIGHT
                }

                else -> {
                    textStartX += startX
                    Paint.Align.LEFT
                }
            }

            val fm = textPaint.fontMetrics
            val drawTop: Float
            val drawBottom: Float
            if (isBottom) {
                drawTop = y + it.lineWidth / 2f
                drawBottom = drawTop + fm.descent - fm.ascent
            } else {
                drawBottom = y - it.lineWidth / 2f
                drawTop = drawBottom - (fm.descent - fm.ascent)
            }

            it.drawRectF.set(0f, drawTop, 0f, drawBottom)
            val textStartY = if (isBottom) {
                it.drawRectF.top - fm.ascent + it.labelYOffset
            } else {
                it.drawRectF.bottom - fm.descent - it.labelYOffset
            }

            textPaint.textSize = it.labelTextSize
            canvas.drawText(it.label!!, textStartX, textStartY, textPaint)
        }

        canvas.restore()

        ObjectPool.recycleRectF(tempRectF)
        ObjectPool.recyclePath(tempPath)
        ObjectPool.recyclePaint(textPaint)
    }
}
